/**
 * 
 */
package il.ac.bgu.cs.heuristics.graph.pddl.parser.types;

import il.ac.bgu.cs.heuristics.utils.CollectionUtils;
import il.ac.bgu.cs.heuristics.utils.StringUtils;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * An immutable representation of a predicate in the pddl domain
 * 
 * @author Ronen Pinko
 * 
 */
public final class Predicate { // NOPMD builder pattern

	private final int						arity;
	private final List<AbstractParameter>	parameters;
	private final String					name;
	private final LogicalOperator			operator;
	/**
	 * a template for a string representation, used by
	 * {@link String#format(String, Object...)}
	 */
	private final String					stringFormatTemplate;
	private final String					stringRepresentation;

	private Predicate(Builder builder) {
		this.name = builder.predicateName;
		this.parameters = builder.parameters;
		this.arity = builder.arity;
		this.operator = builder.logicalOperator;
		this.stringFormatTemplate = createStringFormatTemplate();
		this.stringRepresentation = createStringRepresentation(stringFormatTemplate);
	}

	public int arity() {
		return arity;
	}

	public List<AbstractParameter> getParameters() {
		return Collections.unmodifiableList(parameters);
	}

	public String getName() {
		return name;
	}

	public LogicalOperator getOperator() {
		return operator;
	}

	/**
	 * WARNING: do not use this method to serialize/deserialize as it is subject
	 * to change (RP)
	 * 
	 * @return String format template. for example if predicate is (at ?x ?y)
	 *         this method will return "(at %1 %2)" where %1 and %2 can be
	 *         formated with {@link String#format(String, Object...)}
	 */
	public String getStringTemplate() {
		return stringFormatTemplate;
	}

	/**
	 * WARNING: this representation is subject to change and should never be
	 * used to perform serialization of this object (RP)
	 */
	@Override
	public String toString() {
		return stringRepresentation;
	}

	private String createStringRepresentation(String formatTemplate) {
		final String predicate = String.format(formatTemplate, StringUtils
				.prefixElementsWith(parameters, "?").toArray());
		if (LogicalOperator.NONE.equals(operator)) {
			return predicate;
		} else {
			final StringBuilder sb = new StringBuilder();
			sb.append('(');
			sb.append(operator);
			sb.append(' ');
			sb.append(predicate);
			sb.append(')');
			return sb.toString();
		}
	}

	private String createStringFormatTemplate() {
		final StringBuilder sb = new StringBuilder();
		sb.append('(');
		sb.append(name);
		final Iterator<AbstractParameter> argsIt = parameters.iterator();
		int i = 1;
		while (argsIt.hasNext()) {
			sb.append(' ');
			argsIt.next();
			sb.append('%');
			sb.append(i++);
			sb.append("$s");
			if (argsIt.hasNext()) {
				sb.append(' ');
			}

		}
		sb.append(')');
		return sb.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + arity;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final Predicate other = (Predicate) obj;
		if (arity != other.arity) {
			return false;
		}
		if (name == null) {
			if (other.name != null) {
				return false;
			}
		} else if (!name.equals(other.name)) {
			return false;
		}
		return true;
	}

	public boolean isTypesAgree(List<ITypedConstant> typedList) {
		List<AbstractParameter> AbstractParameterList = getParameters();
		for (int i = 0; i < typedList.size(); i++) {
			boolean isTypeMatch = false;
			for (Type type : AbstractParameterList.get(i).getPossibleTypes()) {
				if (typedList.get(i).ofType(type)) {
					isTypeMatch = true;
					continue;
				}
			}
			if (!isTypeMatch)
				return false;
		}
		return true;
	}

	public void createTypeHierarchyForParams(
			Map<Type, List<Type>> typesHierarchy) {
		for (AbstractParameter param : parameters) {
			param.addPossiableTypes(typesHierarchy);
		}
	}

	public final static class Builder extends ExpressionBuilder {

		private String							predicateName;
		private int								arity		= 0;
		private final List<AbstractParameter>	parameters	= CollectionUtils
																	.newArrayList();

		public Predicate build() {
			return new Predicate(this); // NOPMD builder pattern
		}

		public Builder name(String s) {
			this.predicateName = s;
			return this;
		}

		public Builder addVariable(AbstractParameter parameter) {
			arity++;
			parameters.add(parameter);
			return this;
		}

	}

}